(ns merpg.2D.animation
  (:require [merpg.2D.core :refer :all]
            [merpg.2D.make-game :refer [make-game]]
            [merpg.utils :refer :all]))

(def ^:dynamic *frame-interval* 100)

(defn Image? [img]
  (instance? java.awt.image.BufferedImage img))

(defn animation [{up-path :up
                  down-path :down
                  left-path :left
                  right-path :right} & {:keys [x y angle visible? count-of-frames] :or {x 0 y 0 angle 0 visible? true count-of-frames 4}}]
;  {:pre [(reduce #(and %1 %2) (map string? [up-path down-path left-path right-path]))]}
  (binding [*frame-interval* 10]
  (let [character-direction (atom :down)]
    (set-validator! character-direction #(in? [:up :down :left :right] %))
    (let [          
          imgs {:up (if (Image? up-path)
                      up-path
                      (image up-path))
                :down (if (Image? down-path)
                        down-path
                        (image down-path))
                :left (if (Image? left-path)
                        left-path
                        (image left-path))
                :right (if (Image? right-path)
                         right-path
                         (image right-path))}
          widths {:up (width (:up imgs))
                  :down (width (:down imgs))
                  :left (width (:left imgs))
                  :right (width (:right imgs))}
          heights {:up (height (:up imgs))
                   :down (height (:down imgs))
                   :left (height (:left imgs))
                   :right (height (:right imgs))}
          frame-widths {:up (/ (:up widths) count-of-frames)
                        :down (/ (:down widths) count-of-frames)
                        :left (/ (:left widths) count-of-frames)
                        :right (/ (:right widths) count-of-frames)}
          
          count-left-x (fn [index]
                         (* index (@character-direction frame-widths)))
          create-frameset (memoize (fn create-frameset [index character-direction]
                                     (lazy-seq
                                      (let [frame-width (character-direction frame-widths)
                                            img (character-direction imgs)
                                            height (character-direction heights)
                                            left-x (count-left-x index)
                                            frame (image frame-width height)
                                            is-last? (> (count-left-x index) (character-direction widths))]
                                        (try
                                          (draw-to-surface frame
                                                           (Draw (subimage img left-x 0 frame-width height) [0 0]))
                                          (cons frame
                                                (create-frameset (inc index) character-direction)) ;; TODO Hyiei. Ei saisi ohjata koodia poikkeuksilla. Korjaa is-last? kun jaksat.
                                          (catch java.awt.image.RasterFormatException ex
                                        ;                                            (println "Rasterformatex napattu")
                                        ;(println "Left-x " left-x " frame-width " frame-width " width " (width img))
                                            nil))))))]
      (let [last-frame-change-moment (ref (timer))
            frames (atom (create-frameset 0 @character-direction)) 
            frame-index (ref 0)
            dump-state (fn []
                         (println "Last-frame-moment " @last-frame-change-moment)
                         (println "Frame-index " @frame-index)
                         (println "Count of frames " (count @frames)))
            toret (object (nth @frames @frame-index) :x x :y y :angle angle :visible? visible?)]
        (assoc toret
          :animated-object? true
          :width (fn []
                   (width (nth @frames @frame-index)))
          :height (fn []
                   (height (nth @frames @frame-index)))
          :set-direction! (fn [dir]
                            (println ":set-direction! called")
                            (reset! character-direction dir)
                            (reset! frames (create-frameset 0 dir))
                            (println "Direction and frames reset to " dir))
          :change-frame-fn!
          (fn []
            (if (> (timer)
                   (+ @last-frame-change-moment *frame-interval*))
              (dosync
               
               (ref-set (:img toret)
                        (try
                          (nth @frames
                               (if-not (>= (inc @frame-index) (count @frames))
                                 (alter frame-index inc)
                                 (ref-set frame-index 0)))
                          (catch IndexOutOfBoundsException ex
                            (println "change-frame-fn!:ssä räjähti")
                            (dump-state)
                            (throw ex))))
               (ref-set last-frame-change-moment (timer)))))))))))
                  ;(println "Not changing img")))))))))


(defmacro def-inherited-method "Works only with the static-/anim-object tree..."
  [method params & {:keys [returns-this?] :or {returns-this? true}} ]
  (if returns-this?
    `(defmethod ~method :animated-object ~params
       (->  (assoc ~'this :animated-object? false)
            (~method ~@(filter #(not (= 'this %)) params))
            (assoc :animated-object? true)))
    `(defmethod ~method :animated-object ~params
       (->  (assoc ~'this :animated-object? false)
            (~method ~@(filter #(not (= 'this %)) params))))))


(defmacro def-inherited-methods [& methods]
  (when-let [[method-name returns-this? & params] (first methods)]
    `(do
       (def-inherited-method ~method-name ~@params :returns-this? ~returns-this?)
       (def-inherited-methods ~@(rest methods)))))

(def-inherited-methods
  [position-at true [this [x y]]]
  [location false [this]]
  [move true [this how-much]]
  [Dimensions false [this]]
  [width false [this]]
  [height false [this]])

;; Koodaa nämä piirtämäänn eri freimi aina n/b sekunnin välein..

(defmethod Draw [:animated-object 0]
  [this]
  ;(println "Drawing anim-obj 0")
  (try
    (Draw @(:img this) (map-to-vec (location this)))
    (catch clojure.lang.ArityException ex
      (println "Arityräjähdys")
      (throw ex)))
  ((:change-frame-fn! this)))

(defmethod Draw [:animated-object 1]
  [this coordinates]
    ;(println "Drawing anim-obj 1")
  (Draw @(:img this) (map-to-vec coordinates))
  (println (map-to-vec coordinates))
  ((:change-frame-fn! this)))


(defn Varsi-animaatio-testikoodi []
  (let [loc (atom {:x 0 :y 0 :Angle 0 })]
    (make-game {:varsi (animation {:up "./varsi-ylos.jpg"
                                   :down "./varsi-alas.jpg"
                                   :left "./varsi-vasen.jpg"
                                   :right "./varsi-oikea.jpg"}
                                  :x 50 :y 100 :angle 45)}
               :update (fn [{varsi :varsi}]
                         (let [{x :x
                                kulma :Angle
                                y :y} varsi
                                movage 20]
                           (swap! loc #(-> %
                                           (assoc :x x)
                                           (assoc :y y)
                                           (assoc :Angle kulma)))
                           (let [varsi (if (key-down? :up)
                                         (do
                                           ((:set-direction! varsi) :up) ;; :set-direction! returns rubbish...
                                           (->
                                            (assoc varsi :Angle 270)
                                            (move movage)))
                                         varsi)]
                             {:varsi (if (key-down? :right)
                                       (do
                                         ((:set-direction! varsi) :right)
                                         (->
                                          (assoc varsi :Angle 0)
                                          (move movage)))
                                       (if (key-down? :left)
                                         (do
                                           ((:set-direction! varsi) :left)
                                           (->
                                            (assoc varsi :Angle 180)
                                            (move movage)))
                                         (if (key-down? :down)
                                           (do
                                             ((:set-direction! varsi) :down)
                                             (->
                                              (assoc varsi :Angle 90)
                                              (move movage)))
                                         varsi)))})))
               :post-drawqueue
               #(with-color "#0000FF"
                  ;(println "Sehän piirtää")
                  (let [x (/ (width) 2)
                        y (/ (height) 2)]
                    
                    (Draw "Hello world!" [x y])
                    (let [{äxä :x yxy :y angle :Angle w :w h :h} @loc]
                      (Draw (str "Location: " äxä ", " yxy "    - Angle: " angle ) [480 50])                             
                      (comment
                        (with-color "#00FF00"
                          (let [x2 (+ äxä (* (cos angle) 70))
                                y2 (+ yxy (* (sin angle) 70))]
                            (Line äxä yxy x2 y2)
                            (with-color "#FF0000"
                              (Oval (- x2 (/ 50 2)) (- y2 (/ 50 2)) 50 50 :fill? true))))))
                    
                    (with-color "#000000"
                      (Draw (str "Is up pressed? " (key-down? :up)) [50 50])
                      (Draw (str "Is down pressed? " (key-down? :down)) [50 60])
                      (Draw (str "Is right pressed? " (key-down? :right)) [50 70])
                      (Draw (str "Is left pressed? " (key-down? :left)) [50 80])
                      (Draw (str "Is ctrl pressedr? " (key-down? :ctrl)) [50 90])
                      (try
                        (Draw (str "Is shift? " (key-down? :shift)) [50 100])
                        (catch ClassCastException ex
                          (println "CCE napattu O.o")
                          (throw ex)))
                      (Draw (str "Is esc? " (key-down? :esc)) [50 110])))))))
